Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto traits #54

Merged
merged 14 commits into from
Jul 17, 2017
Merged

Auto traits #54

merged 14 commits into from
Jul 17, 2017

Conversation

scalexm
Copy link
Member

@scalexm scalexm commented Jul 11, 2017

Implement auto traits and negative impls.

  • For an auto trait AutoTrait, we generate a default impl for each type MyType where MyType: (!)AutoTrait does not unify with an existing positive or negative impl. A default impl will basically retrieve the field types FieldType1<...>, ..., FieldTypeN<...> within MyType and generate the impl:
impl<...> AutoTrait for MyType where FieldType1<...>: AutoTrait, ..., FieldTypeN<...>: AutoTrait
  • Negative impls do not generate any rule during lowering and are just here to deactivate default implementations (although they can be implemented for any trait, not just auto traits). Negative impls can overlap arbitrarily. Positive and negative impls for a same trait cannot overlap. Negative impls cannot define associated type values.

  • Goals of the form T: AutoTrait have coinductive semantics. If we encounter a cycle whose all components have coinductive semantics, we accept the cycle as an infinite proof and return Ok without performing any substitution.

  • The syntax for auto traits is #[auto] trait Send { }

@@ -47,12 +47,17 @@ StructDefn: StructDefn = {
}
};

AutoKeyword: () = "#" "[" "auto" "]";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you wanted to make auto usable as an identifier, you could do this:

Id: Identifier = {
    <l:@L> <s:RawIdent> <r:@R> => Identifier {
        str: intern(s),
        span: Span::new(l, r),
    }
};

RawIdent: &'input str = {
    "auto",
    r"([A-Za-z]|_)([A-Za-z0-9]|_)*",
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I see, but actually I think #[auto] is fine now :)

// if all the components of the cycle also have coinductive semantics, we accept
// the cycle `(?0: AutoTrait) :- ... :- (?0: AutoTrait)` as an infinite proof for
// `?0: AutoTrait` and we do not perform any substitution.
if self.stack.iter().skip(index).all(|s| s.goal.is_coinductive(&*self.program)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I missed chain(Some(goal.clone())) here, adding that...

Copy link
Contributor

@nikomatsakis nikomatsakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Left a few minor comments.

src/ir/mod.rs Outdated
@@ -84,8 +87,11 @@ impl Environment {
pub fn add_clauses<I>(&self, clauses: I) -> Arc<Environment>
where I: IntoIterator<Item = DomainGoal>
{
use std::collections::HashSet;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not that I mind, but why not move this to the top of the module?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm perfectly ok with moving it to the top. I don't know why but some times I prefer local imports if they're used only at one little place. Not every time though haha.

}
}

fn provide_impl_for(&self, trait_ref: TraitRef, struct_datum: &StructDatum) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: let's call this impl_provided_for -- this name makes it sound to me like the fn is going to synthesize an impl

Copy link
Member Author

@scalexm scalexm Jul 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, actually I was reading this like "self provide_impl_for struct ?", but I agree this can be confusing.


goal {
forall<T> {
Vec<T>: Send
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this through me for a bit, but I guess it's true because struct Vec<T> { } has no fields

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, but actually this test is not great, I'm going to change that.

// In fact, as soon as there is a field which is referencing `T` with an indirection like `Foo<Bar<T>>`,
// we need to add the `WellFormed(T)` because the `if (T: Send)` elaborates `WellFormed(T: Send)` but not
// `WellFormed(T)`. This is not an issue, but is maybe a bit inconsistent.
goal {
Copy link
Member Author

@scalexm scalexm Jul 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikomatsakis so actually I added a comment here, which I think is interesting, and which is why I wanted the WF(T) reverse rules at first.

if self.stack.iter()
.skip(index)
.map(|s| &s.goal)
.chain(Some(&goal))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh. =) Funny oversight. Maybe add a test for this? Maybe not worth it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I realised that from a correctness point of view, that's the same thing because we already know that there is a cycle starting at index index, so the very first element of our iterator is also our current goal already. Anyway, let's keep it that way :)

@scalexm
Copy link
Member Author

scalexm commented Jul 13, 2017

Actually the comment I've added on my last commit made me think of something which is not great. Given:

#[auto] trait Send { }
trait Clone { }

struct Clonable<T> where T: Clone { }

struct S<T> where T: Clone {
    data: Clonable<T>
}

// default impl generated:
// impl<T> Send for S<T> where Clonable<T>: Send, WF(Clonable<T>: Send)
//
// WF(Clonable<T>: Send) :- WF(Clonable<T>)
// WF(Clonable<T>) :- WF(T), T: Clone, WF(T: Clone)
// WF(T: Clone) :- WF(T)

we only have:

forall<T> {
    if (WellFormed(T), T: Send, T: Clone) {
        S<T>: Send
    }
}

With implied bounds, it's a bit better:

forall<T> {
    if (WellFormed(T), WellFormed(S<T>), T: Send) {
        S<T>: Send
    }
}

Basically it exposes WF checks for fields.

I don't know if this is a problem, but it feels awkward. I think we shouldn't expand the WF bounds on default implementations? This seems fine because expanding WF bounds on impls is only there to ensure that in things like:

trait SuperTrait { }
trait Trait : SuperTrait { }

impl<T> Foo for MyStruct<T> where T: Trait { }

we correctly check that T implements both Trait and SuperTrait to conclude that MyStruct<T>: Foo. There are no such bounds on default implementations.

As was discussed previously in PR #49, we shouldn't expose WF checks for fields (especially for proving things like S<T>: Send).

@nikomatsakis
Copy link
Contributor

@scalexm remind me, are we waiting for any further changes here?

@nikomatsakis
Copy link
Contributor

I guess we decided to tweak (or maybe tweak) how we approach the WF rules, right... I remember now =)

@scalexm
Copy link
Member Author

scalexm commented Jul 15, 2017

@nikomatsakis Removing the WF(FieldType: Send) bounds on default implementations is fine for the moment (I did not push my commit yet, I'll do that within a few hours).

I'll open a second PR later for tweaking WF rules + implied bounds (I think what we discussed on Gitter works pretty well).

@nikomatsakis nikomatsakis merged commit 7eb0f08 into rust-lang:master Jul 17, 2017
@scalexm scalexm deleted the cologic branch July 17, 2017 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants